//! Advertising API. Generated with `dbus-codegen-rust -s -d org.bluez -c nonblock -m None -p /org/bluez/hci0`.

use std::collections::HashMap;
use std::fmt;
use std::sync::Arc;

use dbus;
use dbus::arg;
use dbus::nonblock;
use dbus::tree;

pub trait OrgBluezLEAdvertisingManager1 {
    fn register_advertisement(
        &self,
        advertisement: dbus::Path,
        options: ::std::collections::HashMap<&str, arg::Variant<Box<dyn arg::RefArg>>>,
    ) -> nonblock::MethodReply<()>;
    fn unregister_advertisement(&self, service: dbus::Path) -> nonblock::MethodReply<()>;
    fn active_instances(&self) -> nonblock::MethodReply<u8>;
    fn supported_instances(&self) -> nonblock::MethodReply<u8>;
    fn supported_includes(&self) -> nonblock::MethodReply<Vec<String>>;
}

impl<'a, T, C> OrgBluezLEAdvertisingManager1 for nonblock::Proxy<'a, C>
where
    T: nonblock::NonblockReply,
    C: ::std::ops::Deref<Target = T>,
{
    fn register_advertisement(
        &self,
        advertisement: dbus::Path,
        options: ::std::collections::HashMap<&str, arg::Variant<Box<dyn arg::RefArg>>>,
    ) -> nonblock::MethodReply<()> {
        self.method_call(
            "org.bluez.LEAdvertisingManager1",
            "RegisterAdvertisement",
            (advertisement, options),
        )
    }

    fn unregister_advertisement(&self, service: dbus::Path) -> nonblock::MethodReply<()> {
        self.method_call(
            "org.bluez.LEAdvertisingManager1",
            "UnregisterAdvertisement",
            (service,),
        )
    }

    fn active_instances(&self) -> nonblock::MethodReply<u8> {
        <Self as nonblock::stdintf::org_freedesktop_dbus::Properties>::get(
            &self,
            "org.bluez.LEAdvertisingManager1",
            "ActiveInstances",
        )
    }

    fn supported_instances(&self) -> nonblock::MethodReply<u8> {
        <Self as nonblock::stdintf::org_freedesktop_dbus::Properties>::get(
            &self,
            "org.bluez.LEAdvertisingManager1",
            "SupportedInstances",
        )
    }

    fn supported_includes(&self) -> nonblock::MethodReply<Vec<String>> {
        <Self as nonblock::stdintf::org_freedesktop_dbus::Properties>::get(
            &self,
            "org.bluez.LEAdvertisingManager1",
            "SupportedIncludes",
        )
    }
}

pub struct OrgBluezLEAdvertisingManager<'a, T, C>
where
    T: nonblock::NonblockReply,
    C: ::std::ops::Deref<Target = T>,
{
    proxy: nonblock::Proxy<'a, C>,
}

impl<'a, T, C> OrgBluezLEAdvertisingManager<'a, T, C>
where
    T: nonblock::NonblockReply,
    C: ::std::ops::Deref<Target = T>,
{
    pub fn new(proxy: nonblock::Proxy<'a, C>) -> Self {
        Self { proxy }
    }

    pub fn proxy(&self) -> &nonblock::Proxy<'a, C> {
        &self.proxy
    }
}

#[derive(Debug, Eq, PartialEq, Copy, Clone)]
pub enum OrgBluezLEAdvertisement1Type {
    Broadcast,
    Peripheral,
}

impl Default for OrgBluezLEAdvertisement1Type {
    fn default() -> Self {
        Self::Broadcast
    }
}

impl fmt::Display for OrgBluezLEAdvertisement1Type {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
        match *self {
            Self::Broadcast => write!(f, "broadcast"),
            Self::Peripheral => write!(f, "peripheral"),
        }
    }
}

#[derive(Default)]
pub struct OrgBluezLEAdvertisement {
    path: String,
    r#type: OrgBluezLEAdvertisement1Type,
    service_uuids: Option<Vec<String>>,
    manufacturer_data: Option<HashMap<u16, Vec<u8>>>,
    solicit_uuids: Option<Vec<String>>,
    service_data: Option<HashMap<String, Vec<u8>>>,
    data: Option<HashMap<u8, Vec<u8>>>,
    discoverable: Option<bool>,
    discoverable_timeout: Option<u16>,
    includes: Option<Vec<String>>,
    local_name: Option<String>,
    apperance: Option<u16>,
    duration: Option<u16>,
    timeout: Option<u16>,
    secondary_channel: Option<String>,
    release_fn: Option<Box<dyn Fn(&OrgBluezLEAdvertisement)>>,
}

impl fmt::Debug for OrgBluezLEAdvertisement {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.path)
    }
}

impl OrgBluezLEAdvertisement {
    /// Create an advertisement.
    ///
    /// NOTE: An advertisement could not be changed after registered into the advertisement manager.
    pub fn new(
        path: &str,
        r#type: OrgBluezLEAdvertisement1Type,
        release_fn: Box<dyn Fn(&OrgBluezLEAdvertisement)>,
    ) -> Self {
        let mut s = Self::default();
        s.path = path.to_string();
        s.r#type = r#type;
        s.release_fn = Some(release_fn);
        s
    }

    pub fn path(&self) -> &str {
        self.path.as_str()
    }

    pub fn add_service_uuid(&mut self, uuid: &str) {
        let service_uuids = self.service_uuids.get_or_insert(Vec::default());
        service_uuids.push(uuid.to_string());
    }

    pub fn add_manufacturer_data(&mut self, manuf_code: u16, data: Vec<u8>) {
        let manufacturer_data = self.manufacturer_data.get_or_insert(HashMap::default());
        manufacturer_data.insert(manuf_code, data);
    }

    pub fn add_solicit_uuid(&mut self, uuid: &str) {
        let solicit_uuids = self.solicit_uuids.get_or_insert(Vec::default());
        solicit_uuids.push(uuid.to_string());
    }

    pub fn add_service_data(&mut self, uuid: &str, data: Vec<u8>) {
        let service_data = self.service_data.get_or_insert(HashMap::default());
        service_data.insert(uuid.to_string(), data);
    }

    pub fn add_data(&mut self, ad_type: u8, data: Vec<u8>) {
        let d = self.data.get_or_insert(HashMap::default());
        d.insert(ad_type, data);
    }

    pub fn add_discoverable(&mut self, discoverable: bool) {
        self.discoverable.get_or_insert(discoverable);
    }

    pub fn add_discoverable_timeout(&mut self, timeout: u16) {
        self.discoverable_timeout.get_or_insert(timeout);
    }

    pub fn add_include(&mut self, include: &str) {
        self.includes
            .get_or_insert(Vec::default())
            .push(include.to_string());
    }

    pub fn add_local_name(&mut self, name: &str) {
        self.local_name.get_or_insert(name.to_string());
    }

    pub fn add_apperance(&mut self, apperance: u16) {
        self.apperance.get_or_insert(apperance);
    }

    pub fn add_duration(&mut self, duration: u16) {
        self.duration.get_or_insert(duration);
    }

    pub fn add_timeout(&mut self, timeout: u16) {
        self.timeout.get_or_insert(timeout);
    }

    pub fn add_secondary_channel(&mut self, channel: &str) {
        self.secondary_channel.get_or_insert(channel.to_string());
    }
}

impl tree::DataType for OrgBluezLEAdvertisement {
    type Tree = ();
    type ObjectPath = Arc<OrgBluezLEAdvertisement>;
    type Interface = ();
    type Property = ();
    type Method = ();
    type Signal = ();
}

pub fn register_advertisement<'a, T, C, A>(
    manager: OrgBluezLEAdvertisingManager<'a, T, C>,
    advertisement: A,
) where
    T: nonblock::NonblockReply,
    C: ::std::ops::Deref<Target = T>,
    A: Into<Arc<OrgBluezLEAdvertisement>>,
{
    let advertisement: Arc<OrgBluezLEAdvertisement> = advertisement.into();
    let f = tree::Factory::new_sync::<OrgBluezLEAdvertisement>();

    /* Add a method on path  */
    let obj = f.object_path(advertisement.path().to_string(), advertisement.clone());
    let obj = obj.introspectable();

    /* Interface */
    let mut inf = f.interface("org.bluez.LEAdvertisement1", ());

    /* Properties - Types */
    let p = f
        .property::<String, _>("Type", ())
        .access(tree::Access::Read)
        .on_get(|i, p| {
            let t = p.path.get_data().r#type;
            i.append(t.to_string());
            Ok(())
        });
    inf = inf.add_p(p);

    /* Properties - ServiceUUIDs */
    if advertisement.service_uuids.is_some() {
        let p = f
            .property::<Vec<String>, _>("ServiceUUIDs", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().service_uuids.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - ManufacturerData */
    if advertisement.manufacturer_data.is_some() {
        let p = f
            .property::<HashMap<u16, Vec<u8>>, _>("ManufacturerData", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().manufacturer_data.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - SolicitUUIDs*/
    if advertisement.solicit_uuids.is_some() {
        let p = f
            .property::<Vec<String>, _>("SolicitUUIDs", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().solicit_uuids.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - ServiceData */
    if advertisement.service_data.is_some() {
        let p = f
            .property::<HashMap<String, Vec<u8>>, _>("ServiceData", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().service_data.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Data */
    if advertisement.data.is_some() {
        let p = f
            .property::<HashMap<u8, Vec<u8>>, _>("Data", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().data.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Discoverable */
    if advertisement.discoverable.is_some() {
        let p = f
            .property::<bool, _>("Discoverable", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().discoverable.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - DiscoverableTimeout */
    if advertisement.discoverable_timeout.is_some() {
        let p = f
            .property::<u16, _>("DiscoverableTimeout", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().discoverable_timeout.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Includes */
    if advertisement.includes.is_some() {
        let p = f
            .property::<Vec<String>, _>("Includes", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().includes.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - LocalName */
    if advertisement.local_name.is_some() {
        let p = f
            .property::<String, _>("LocalName", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().local_name.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Apperance */
    if advertisement.apperance.is_some() {
        let p = f
            .property::<u16, _>("Apperance", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().apperance.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Duration */
    if advertisement.duration.is_some() {
        let p = f
            .property::<u16, _>("Duration", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().duration.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - Timeout */
    if advertisement.timeout.is_some() {
        let p = f
            .property::<u16, _>("Timeout", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().timeout.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Propertity - SecondaryChannel */
    if advertisement.secondary_channel.is_some() {
        let p = f
            .property::<String, _>("SecondaryChannel", ())
            .access(tree::Access::Read)
            .on_get(|i, p| {
                i.append(p.path.get_data().secondary_channel.clone().unwrap());
                Ok(())
            });
        inf = inf.add_p(p);
    }

    /* Method - Release */
    let release_m = f.method("Release", (), move |m| {
        let adv = m.path.get_data();
        if let Some(release_fn) = &adv.release_fn {
            release_fn(adv)
        }
        Ok(vec![])
    });
    let inf = inf.add_m(release_m);

    obj.add(inf);
}
